package edu.nju.mutest;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.PackageDeclaration;
import edu.nju.mutest.combination.AddStrategy;
import edu.nju.mutest.combination.CartesianStrategy;
import edu.nju.mutest.combination.MutatorCombinationStrategy;
import edu.nju.mutest.mutator.factory.*;
import edu.nju.mutest.util.CompilationUnitUtil;
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

/**
 * Source-level mutation engine using javaParser.
 */
public class SrcMutationEngine {

    private static final Map<String, MutatorFactory> mutatorName2FactoryMap = Map.of(
            "abs", new ABSMutatorFactory(),
            "aor", new AORMutatorFactory(),
            "lcr", new LCRMutatorFactory(),
            "ror", new RORMutatorFactory(),
            "uoi", new UOIMutationFactory(),
            "b", new BinaryMutatorFactory(),
            "nl", new NumericalLiteralMutatorFactory()
    );

    private static final Map<String, MutatorCombinationStrategy> combineStrategyMap = Map.of(
            "add", new AddStrategy(),
            "cartesian", new CartesianStrategy()
    );

    /**
     * @param args arguments imported.
     *             (1) arg[0], source_java_file: the path to a folder, or a .java file.
     *             (2) arg[1], mutant_pool_dir: the path to the folder to restore mutants.
     *             (3) arg[2], mutators: a list of mutator names seperated by ','. Example: "abs,aor". all mutator will
     *                                   be selected by default.
     *             (4) arg[3], mutator combination: cross combine all mutators if "cartesian", else mutate separately.
     */
    public static void main(String[] args) throws IOException {

        if (args.length < 2) {
            System.out.println("DemoSrcMutationEngine: " +
                    "<source_java_file> <mutant_pool_dir> [mutators] [mutator combination]");
            System.out.println("received args: " + Arrays.toString(args));
            return;
        }

        // Read in original program(s).
        File srcFile = new File(args[0]);
        System.out.println("[LOG] Source file: " + srcFile.getAbsolutePath());
        File outDir = new File(args[1]);
        System.out.println("[LOG] Output dir: " + outDir.getAbsolutePath());
        // all mutators are selected by default.
        String[] chosenMutatorNames = mutatorName2FactoryMap.keySet().toArray(new String[0]);
        if (args.length > 2)
            chosenMutatorNames = args[2].split(",");

        Optional<CompilationUnit> cuSrcOptional = CompilationUnitUtil.parse(srcFile);
        if (cuSrcOptional.isEmpty()) {
            System.out.println("Parsing Java source file failed");
            return;
        }

        CompilationUnit cuSrc = cuSrcOptional.get();
        List<CompilationUnit> mutantList = new ArrayList<>();
        MutatorCombinationStrategy mcs = combineStrategyMap.getOrDefault(
                args.length > 3 ? args[3] : "",
                new AddStrategy()
        );

        // traverse all mutators that user selected
        for (String name : chosenMutatorNames) {
            System.out.println("[LOG] process mutator: " + name);
            mcs.generateMutants(mutantList, cuSrc, mutatorName2FactoryMap.get(name));
        }
        System.out.printf("[LOG] Generate %d mutants.%n", mutantList.size());

        // Preserve to local.
        preserveToLocal(outDir, srcFile.getName(), mutantList);
    }

    /**
     * Write mutants to disk.
     */
    private static void preserveToLocal(
            File outDir,
            String outputFileName,
            List<CompilationUnit> mutants) throws IOException {

        if (mutants.isEmpty()) {
            return;
        }
        // Recreate outDir if it is existed.
        if (outDir.exists()) {
            FileUtils.forceDelete(outDir);
            System.out.println("[LOG] Delete existing outDir.");
        }
        // if new directory is created successfully.
        if (outDir.mkdirs()) {
            System.out.println("[LOG] Create outDir: " + outDir.getAbsolutePath());
        }
        else {
            System.out.println("[LOG] Creating outDir failed: " + outDir.getAbsolutePath());
        }

        for (int i = 0; i < mutants.size(); i++) {
            // Create directory to preserve the mutant
            File outputDir = new File(outDir, String.format("mut-%d/%s", i + 1, getPathFromPackage(mutants.get(0))));
            if (outputDir.mkdirs()) {
                System.out.println("[LOG] Create src file dir: " + outputDir.getAbsolutePath());
            }
            // Write mutant to local.
            File outputFile = new File(outputDir, outputFileName);
            try (FileWriter fw = new FileWriter(outputFile)) {
                fw.write(mutants.get(i).toString());
            } catch (Exception e) {
                System.err.println(Arrays.toString(e.getStackTrace()));
            }
        }
    }

    private static String getPathFromPackage(CompilationUnit cu) {
        Optional<PackageDeclaration> opPD = cu.getPackageDeclaration();
        if (opPD.isPresent()) {
            // Turn package info like 'edu.nju.ise' to path 'edu/nju/ise/'
            String packInfo = opPD.get().getName().asString();
            return packInfo.replace('.', '/');
        }
        return "";
    }
}
